Skip to content

Feat/local model providers - #1

Merged
ravituringworks merged 11 commits into
masterfrom
feat/local-model-providers
Aug 9, 2026
Merged

Feat/local model providers#1
ravituringworks merged 11 commits into
masterfrom
feat/local-model-providers

Conversation

@ravituringworks

Copy link
Copy Markdown

No description provided.

ravituringworks and others added 11 commits August 8, 2026 12:08
Both recipes declared embeddings only, so a local install could index and
search a brain but never reason over it — every synthesis step routed to a
hosted provider because no local recipe offered a chat touchpoint.

Both now declare chat and expansion with tool calling, which is what lets
the subagent loop run locally. Model lists are advisory (openai-compat tier
never rejects an unlisted id), so any model the user pulled or launched
resolves. Costs are pinned to 0 — a nonzero rate here would corrupt every
--max-usd pre-flight for a local brain.

Also adds a reachability probe to the ollama recipe (llama-server already
had one) and threads baseURL through probeOllama, so a config-only URL
override is visible to the probe instead of silently checking localhost.
A stopped daemon is the local failure mode no key check can catch.

Drops ollama from the "embedding-only, use a hosted provider for chat"
hint in assertTouchpoint; the remaining voyage case now points at the local
lane first, since a user who hits that wall is usually trying to avoid a
hosted key rather than shopping for one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four openai-compatible providers, closing the gap against the provider set
carried by sibling projects. All are chat + expansion; fireworks also
serves embeddings (nomic-embed-text-v1.5, Matryoshka 64..768), making it
the only one of the four that can run a whole brain alone.

Endpoints, auth env vars and capability flags were verified against each
provider's live documentation rather than recalled — several model ids and
context limits had moved. supports_structured_outputs is set only where
the provider documents a strict json_schema response_format (cerebras,
fireworks); the rest stay on the schemaless expansion path, where a wrong
false costs one retry and a wrong true breaks expand().

Cost is left undefined for cerebras, fireworks and sambanova: none
publishes a stable public per-token table, and a fabricated rate corrupts
a --max-usd budget gate silently, which is worse than an absent one. xai
publishes rates and carries its sub-200k-prompt tier, with the 2x
above-200k tier noted in the recipe.

Excluded deliberately: cerebras zai-glm-4.7 (scheduled for deprecation
nine days out) and sambanova's preview tier, so the wizard cannot default
onto a model whose availability moves without notice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…solves

Step 7 of the model-resolution chain returned a hardcoded anthropic:* id.
For a brain with an Anthropic credential that is correct. For a brain
deliberately run without one it is not a default at all — it names a model
the install cannot reach, and the resulting NO_ANTHROPIC_API_KEY reads as
"gbrain requires Anthropic" rather than "nothing told me what to use".

Users hit this after correctly pointing chat_model at a local provider,
because the gateway's chat_model and this tier resolver were two
independent notions of "the default model" and only one was consulted here.

resolveTierDefault() now returns TIER_DEFAULTS whenever hasAnthropicKey()
resolves — keyed brains are byte-identical to before — and otherwise falls
through to the configured chat_model. It stays a pure config read with no
probe: readiness is the doctor's job, not something to put in the path of
every unconfigured call.

Two cases deliberately keep the Anthropic default: no chat_model at all,
and a chat_model that is itself Anthropic. Someone who has configured
nothing has an unfinished setup, not a local brain, and the truthful
missing-key error beats a connection-refused from an invented default.

The result still passes through enforceSubagentCapable, so a chat_model
pointing at a tool-less model cannot become the subagent driver just
because it arrived via this path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two independent blockers made a keyless brain unable to run subagent work,
even though the provider-agnostic gateway tool loop has shipped since v0.38
and supports these providers.

The Anthropic SDK client was constructed at worker-REGISTRATION time. The
SDK constructor throws on a missing key, so `gbrain jobs work` died during
startup on a brain with no Anthropic credential — before any routing
decision was reached, and regardless of which models its jobs targeted. It
is now built lazily on first legacy-path use, and must stay that way.

The handler then refused any non-Anthropic model unless agent.use_gateway_loop
was set, pointing at a config key most users never find. That made a working
loop look like an unsupported configuration. A non-Anthropic model now
auto-routes to the gateway loop, because the legacy path calls the Anthropic
Messages API directly and could never have run it. The flag keeps its real
meaning: it opts Anthropic models into the gateway loop too, which is the
actual rollout decision.

Safety is unchanged. classifyCapabilities() at handler entry already
rejects tool-less models and unknown providers, so auto-routing can only
reach a provider the loop can drive. A once-per-model stderr notice records
which loop ran, since the two differ in cost and cache behavior.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three user-facing surfaces still asserted the pre-fix contract.

The init picker note said subagent features "require ANTHROPIC_API_KEY
regardless of which chat model you pick" — shown at the exact moment a
user deliberately chose a non-Anthropic provider, so a stale claim there
tells them their choice does not count. It now states what actually
differs on another provider: tool-calling quality varies by model, and
without Anthropic's cache markers each turn re-sends the conversation.

The doctor's subagent_capability check warned whenever chat_model was
non-Anthropic without a key. That is now a supported configuration. In its
place the check probes local endpoints directly, which catches the failure
no key check can — a brain pointed at Ollama with the daemon stopped. The
probe passes the config-resolved base URL so it tests the endpoint live
traffic uses, and is bounded at 2s so a hung daemon cannot stall doctor.

think's "no LLM available" text named ANTHROPIC_API_KEY as the fix. It now
names the chat_model config key first. The NO_ANTHROPIC_API_KEY warning
code is unchanged — it is a stable machine-readable signal with existing
consumers; only the human-facing prose moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Seventeen tests covering the single user-visible claim — a brain
configured for a local model runs with no hosted key — plus each of the
four places that claim was independently false.

Serial because the tier-default tests need both a temp GBRAIN_HOME holding
a written config and a cleared ANTHROPIC_API_KEY; a developer machine with
a real key in ~/.gbrain/config.json makes the no-key path untestable
otherwise, the hazard test/helpers/no-anthropic-key.ts documents.

The Anthropic-key-present case is pinned as byte-identical to the previous
behavior, so the fallback cannot silently start firing for keyed brains.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
New docs/guides/local-models.md is the deliverable a user needs: the
three-command setup, why the fix was four separate changes (each a place
the behavior could regress), and an honest account of what local costs —
prompt caching, the 4096-token default context that silently truncates
retrieved context, tool-calling quality on small models, and embedding
quality versus hosted.

Covers remote Ollama and Ollama Cloud, including the trap that -cloud and
:cloud model ids run on Ollama's servers even when the base URL points at
the local daemon. The model id is the only thing that tells you, and it is
a privacy decision rather than a performance one.

Also documents the two-process llama.cpp setup (one model per server,
--jinja required for tool calling) and why three of the four new hosted
providers report cost as unknown.

KEY_FILES entries for model-config.ts, subagent.ts and the openrouter
recipe updated to current state — the openrouter entry claimed
isAnthropicProvider "hard-pins gbrain's subagent infra to Anthropic-direct",
which is no longer the operative gate.

CHANGELOG entry under Unreleased; llms bundles regenerated for the
CLAUDE.md reference-map row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The no-prompt-caching notice said the loop "will run hot (cost scales
linearly with conversation length)" and suggested switching to Anthropic
for lower cost. On Ollama or llama-server the marginal token cost is zero,
so the advice is wrong in both halves — and it is the message a
deliberately-local brain sees on every process.

Free providers now get the accurate version: the loop re-sends the
conversation each turn, so it gets slower, and the fix is a larger context
window rather than a different provider. Detected from the recipe's
declared chat pricing rather than an id allowlist, so a future local
recipe is covered without editing a list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bedrock (SigV4-signed regional hosts), Copilot (OAuth device flow, no
static key) and poolside (enterprise-gated, contract unverifiable against
public docs) each need transport work in the gateway's implementation
switch rather than a recipe, which is pure data over an OpenAI-compatible
endpoint. All three are reachable through the existing litellm proxy
recipe today.

Recording the omission explicitly so the gap is a known decision rather
than something a reader has to infer from an absence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…und-trip guard

`gbrain agent --help` still said non-Anthropic providers "need
agent.use_gateway_loop enabled" and that the worker runs the legacy
Anthropic-direct path by default. Neither is true now — a non-Anthropic
model auto-routes to the gateway loop, and local recipes need no
credential at all. The flag's real remaining purpose (routing ANTHROPIC
models through the gateway loop) is stated instead.

test/config-set.test.ts sourced the garrytan#2753 command string from the doctor
warning this branch removed. The invariant it guards is unchanged and
still worth keeping — the exact command gbrain prints must be accepted by
`config set` without --force — so it now extracts that command from
`gbrain agent --help`, which is the surface that still prints it. Same
drift protection, anchored to a live surface rather than a deleted one.

Adds the inverse assertion the removal earns: doctor must NOT warn about a
non-Anthropic chat_model with no key, since warning would tell users to
fix a configuration that is already correct.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A keyless brain ran all four tiers on one model. That wastes a local fleet
in both directions: a 3B model is the right utility-tier classifier and the
wrong deep-tier reasoner. `gbrain models autotune` reads what the user has
actually pulled and assigns each tier, printing its reasoning and the
context it measured.

Discovery runs ONCE and writes `models.tier.*`. Resolution stays a pure
config read — a network round-trip in front of every unconfigured LLM call
would be a worse regression than the flat defaults it fixes. Wired into
`gbrain init` for Ollama chat models so a fresh local brain is tiered
without a second command, fail-open so an unreachable daemon at install
time (the normal case — people configure before starting `ollama serve`)
degrades to the previous behavior instead of aborting init.

The `completion AND tools` predicate is load-bearing, not defensive.
Several Ollama EMBEDDING models advertise `tools` without `completion`;
`qwen3-embedding:8b` reports `[tools,embedding]` and at 7.6B outranks most
genuine chat models by size, so selecting on `tools` alone puts a model
that cannot generate text into a reasoning tier. Every rejected model is
printed with its reason, so the pick is auditable rather than magic.

Ranking is by on-disk BYTES, not parameter count: quantized and MLX builds
frequently report no parameter count, and a parameter sort silently drops
them to the bottom. Two tier rules are deliberate — utility prefers the
smallest model WITHOUT `thinking` (classification returns a label; reasoning
tokens are overhead), and reasoning takes the runner-up so deep-tier latency
is not paid on every ordinary call. Size is a proxy for capability, not a
quality ranking, which is why each assignment prints its justification and
stays overridable.

Never overwrites a hand-set tier without --force, so re-running after a pull
is safe.

Ollama only, deliberately: llama-server serves one model chosen at launch
and LiteLLM proxies opaque backends with no capability API. Both keep the
single-model behavior.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ravituringworks
ravituringworks merged commit 4f4b775 into master Aug 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant